﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using LaTeXGrapher;

namespace LaTeXGrapherTests
{
    [TestFixture]
    public class UserFunctionTests
    {
        [Test]
        public void SimpleConstantsA()
        {
            EvaluateTest(@"
> a = 1
> b = 2
> c = a + b",
             A(P("a + b", 3.0),
               P("c", 3.0),
               P("a + b + c", 6.0)));
        }

        [Test]
        public void SimpleConstantsB()
        {
            EvaluateTest(@"
> a' = 1
> b = 2
> c = a' + b",
             A(P("a' + b", 3.0),
               P("c", 3.0),
               P("a' + b + c", 6.0)));
        }

        [Test]
        public void SimpleFunctions()
        {
            EvaluateTest(@"
> f(x) = x
> g(y) = f(1) + y
> g'(y) = g(y) - 1",
             A(P("f(3)", 3.0),
               P("g(5)", 6.0),
               P("f(g(1))", 2.0),
               P("g'(1)", 1.0)));
        }

        [Test]
        public void FunctionsOfMultipleArguments()
        {
            EvaluateTest(@"
> add(x,y) = x + y
> sub(x,y) = x - y
> pow(x,y) = x ^ y
> cond(b,x,y) = x | b
                y |
> min'(a,b) = cond(a < b, a, b)",
             A(P("add(1,2)", 1.0 + 2.0),
               P("sub(1,2)", 1.0 - 2.0),
               P("pow(2,4)", Math.Pow(2, 4)),
               P("cond(1 < 2, 3, 4)", 1 < 2 ? 3.0 : 4.0),
               P("cond(1 > 2, 3, 4)", 1 > 2 ? 3.0 : 4.0),
               P("min'(3, 4)", Math.Min(3.0, 4.0)),
               P("min'(4, 3)", Math.Min(4.0, 3.0))));
        }

        [Test]
        public void ConditionalFunctionA()
        {
            EvaluateTest(@"
j(x) = NaN  | x < -1
       x*x  | x <= 3 
       NaN  |
            : x = -1,3",
             A(P("j(-2)", double.NaN),
               P("j(0)", 0.0),
               P("j(3)", 9.0),
               P("j(3.0000001)", double.NaN)));
        }

        [Test]
        public void ConditionalFunctionB()
        {
            EvaluateTest(@"
t(x) = -1   | x == -1
       5    | x = 3 
       1    | x <= -5   
       1/8  | x  >=  8  
       1/4  | x  >  7   
       1/2  | x <> 6    
       1/9  |
            : x = -1,3",
             A(P("t(-1)", -1.0),
               P("t(3)", 5.0),
               P("t(-5)", 1.0),
               P("t(-5.001)", 1.0),
               P("t(7)", 1.0 / 2.0),
               P("t(7.00001)", 1.0 / 4.0),
               P("t(8)", 1.0 / 8.0),
               P("t(8.00001)", 1.0 / 8.0),
               P("t(6)", 1.0 / 9.0),
               P("t(6.0000001)", 1.0 / 2.0),
               P("t(5.9999999)", 1.0 / 2.0)));
        }

        // ----------------------------------- helpers ------------------------------------
        static void EvaluateTest(string data, IEnumerable<KeyValuePair<string,double>> tests)
        {
            FunctionContext c = new FunctionContext();
            FunctionParser parser = new FunctionParser(data); 
            parser.Parse();
            parser.LoadContext(c);

            foreach (var pair in tests)
                TestContextEvaluate(c, pair.Key, pair.Value);
        }

        static void TestContextEvaluate(FunctionContext c, string expression, double expected)
        {
            if (double.IsNaN(expected))
                Assert.IsTrue(double.IsNaN(c.Evaluate(expression)), "On " + expression);
            else
                Assert.AreEqual(expected, c.Evaluate(expression), "On " + expression);
        }

        /// <summary>
        /// Homebrew syntactic sugar hand for make an enumeration.
        /// </summary>
        /// <typeparam name="T">Type of values.</typeparam>
        /// <param name="vals">Values.</param>
        /// <returns>Enumeration of the passed values.</returns>
        static IEnumerable<T> A<T>(params T[] vals)
        {
            return vals.AsEnumerable();
        }

        /// <summary>
        /// Shorthand for make a pair.
        /// </summary>
        /// <typeparam name="T">Type of key.</typeparam>
        /// <typeparam name="U">Type of value.</typeparam>
        /// <param name="key">Key for the pair.</param>
        /// <param name="value">Value for the pair.</param>
        /// <returns>A new KeyValuePair with the passed values.</returns>
        static KeyValuePair<T, U> P<T, U>(T key, U value)
        {
            return new KeyValuePair<T, U>(key, value);
        }
    }
}
